"""
runprofileserver.py

    Starts a lightweight Web server with profiling enabled.

Credits for kcachegrind support taken from lsprofcalltree.py go to:
 David Allouche
 Jp Calderone & Itamar Shtull-Trauring
 Johan Dahlin
"""

import sys
from datetime import datetime

from django.conf import settings
from django.contrib.staticfiles.handlers import StaticFilesHandler
from django.core.management.base import BaseCommand, CommandError
from django.core.servers.basehttp import get_internal_wsgi_application

from django_extensions.management.utils import signalcommand

USE_STATICFILES = "django.contrib.staticfiles" in settings.INSTALLED_APPS


class KCacheGrind:
    def __init__(self, profiler):
        self.data = profiler.getstats()
        self.out_file = None

    def output(self, out_file):
        self.out_file = out_file
        self.out_file.write("events: Ticks\n")
        self._print_summary()
        for entry in self.data:
            self._entry(entry)

    def _print_summary(self):
        max_cost = 0
        for entry in self.data:
            totaltime = int(entry.totaltime * 1000)
            max_cost = max(max_cost, totaltime)
        self.out_file.write("summary: %d\n" % (max_cost,))

    def _entry(self, entry):
        out_file = self.out_file

        code = entry.code
        if isinstance(code, str):
            out_file.write("fn=%s\n" % code)
        else:
            out_file.write("fl=%s\n" % code.co_filename)
            out_file.write("fn=%s\n" % code.co_name)

        inlinetime = int(entry.inlinetime * 1000)
        if isinstance(code, str):
            out_file.write("0  %s\n" % inlinetime)
        else:
            out_file.write("%d %d\n" % (code.co_firstlineno, inlinetime))

        # recursive calls are counted in entry.calls
        if entry.calls:
            calls = entry.calls
        else:
            calls = []

        if isinstance(code, str):
            lineno = 0
        else:
            lineno = code.co_firstlineno

        for subentry in calls:
            self._subentry(lineno, subentry)
        out_file.write("\n")

    def _subentry(self, lineno, subentry):
        out_file = self.out_file
        code = subentry.code
        if isinstance(code, str):
            out_file.write("cfn=%s\n" % code)
            out_file.write("calls=%d 0\n" % (subentry.callcount,))
        else:
            out_file.write("cfl=%s\n" % code.co_filename)
            out_file.write("cfn=%s\n" % code.co_name)
            out_file.write("calls=%d %d\n" % (subentry.callcount, code.co_firstlineno))

        totaltime = int(subentry.totaltime * 1000)
        out_file.write("%d %d\n" % (lineno, totaltime))


class Command(BaseCommand):
    help = "Starts a lightweight Web server with profiling enabled."
    args = "[optional port number, or ipaddr:port]"

    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument(
            "addrport", nargs="?", help="Optional port number, or ipaddr:port"
        )
        parser.add_argument(
            "--noreload",
            action="store_false",
            dest="use_reloader",
            default=True,
            help="Tells Django to NOT use the auto-reloader.",
        )
        parser.add_argument(
            "--nothreading",
            action="store_false",
            dest="use_threading",
            default=True,
            help="Tells Django to NOT use threading.",
        )
        parser.add_argument(
            "--prof-path",
            dest="prof_path",
            default="/tmp",
            help="Specifies the directory which to save profile information in.",
        )
        parser.add_argument(
            "--prof-file",
            dest="prof_file",
            default="{path}.{duration:06d}ms.{time}",
            help='Set filename format, default if "{path}.{duration:06d}ms.{time}".',
        )
        parser.add_argument(
            "--nomedia",
            action="store_true",
            dest="no_media",
            default=False,
            help="Do not profile MEDIA_URL",
        )
        parser.add_argument(
            "--kcachegrind",
            action="store_true",
            dest="use_lsprof",
            default=False,
            help="Create kcachegrind compatible lsprof files, this requires "
            "and automatically enables cProfile.",
        )

        if USE_STATICFILES:
            parser.add_argument(
                "--nostatic",
                action="store_false",
                dest="use_static_handler",
                default=True,
                help="Tells Django to NOT automatically serve static files "
                "at STATIC_URL.",
            )
            parser.add_argument(
                "--insecure",
                action="store_true",
                dest="insecure_serving",
                default=False,
                help="Allows serving static files even if DEBUG is False.",
            )

    @signalcommand
    def handle(self, addrport="", *args, **options):
        import django
        import socket
        import errno
        from django.core.servers.basehttp import run

        if not addrport:
            addr = ""
            port = "8000"
        else:
            try:
                addr, port = addrport.split(":")
            except ValueError:
                addr, port = "", addrport
        if not addr:
            addr = "127.0.0.1"

        if not port.isdigit():
            raise CommandError("%r is not a valid port number." % port)

        use_reloader = options["use_reloader"]
        shutdown_message = options.get("shutdown_message", "")
        no_media = options["no_media"]
        quit_command = (sys.platform == "win32") and "CTRL-BREAK" or "CONTROL-C"

        def inner_run():
            import os
            import time

            import cProfile

            USE_LSPROF = options["use_lsprof"]

            prof_path = options["prof_path"]

            prof_file = options["prof_file"]
            if not prof_file.format(path="1", duration=2, time=3):
                prof_file = "{path}.{duration:06d}ms.{time}"
                print(
                    "Filename format is wrong. "
                    "Default format used: '{path}.{duration:06d}ms.{time}'."
                )

            def get_exclude_paths():
                exclude_paths = []
                media_url = getattr(settings, "MEDIA_URL", None)
                if media_url:
                    exclude_paths.append(media_url)
                static_url = getattr(settings, "STATIC_URL", None)
                if static_url:
                    exclude_paths.append(static_url)
                return exclude_paths

            def make_profiler_handler(inner_handler):
                def handler(environ, start_response):
                    path_info = environ["PATH_INFO"]
                    # when using something like a dynamic site middleware is could be
                    # necessary to refetch the exclude_paths every time since they could
                    # change per site.
                    if no_media and any(
                        path_info.startswith(p) for p in get_exclude_paths()
                    ):
                        return inner_handler(environ, start_response)
                    path_name = path_info.strip("/").replace("/", ".") or "root"
                    profname = "%s.%d.prof" % (path_name, time.time())
                    profname = os.path.join(prof_path, profname)
                    prof = cProfile.Profile()
                    start = datetime.now()
                    try:
                        return prof.runcall(inner_handler, environ, start_response)
                    finally:
                        # seeing how long the request took is important!
                        elap = datetime.now() - start
                        elapms = elap.seconds * 1000.0 + elap.microseconds / 1000.0
                        if USE_LSPROF:
                            kg = KCacheGrind(prof)
                            with open(profname, "w") as f:
                                kg.output(f)
                        else:
                            prof.dump_stats(profname)
                        profname2 = prof_file.format(
                            path=path_name, duration=int(elapms), time=int(time.time())
                        )
                        profname2 = os.path.join(prof_path, "%s.prof" % profname2)
                        os.rename(profname, profname2)

                return handler

            print("Performing system checks...")
            self.check(display_num_errors=True)

            print(
                "\nDjango version %s, using settings %r"
                % (django.get_version(), settings.SETTINGS_MODULE)
            )
            print("Development server is running at http://%s:%s/" % (addr, port))
            print("Quit the server with %s." % quit_command)
            try:
                handler = get_internal_wsgi_application()
                if USE_STATICFILES:
                    use_static_handler = options["use_static_handler"]
                    insecure_serving = options["insecure_serving"]
                    if use_static_handler and (settings.DEBUG or insecure_serving):
                        handler = StaticFilesHandler(handler)
                handler = make_profiler_handler(handler)
                run(addr, int(port), handler, threading=options["use_threading"])
            except socket.error as e:
                # Use helpful error messages instead of ugly tracebacks.
                ERRORS = {
                    errno.EACCES: "You don't have permission to access that port.",
                    errno.EADDRINUSE: "That port is already in use.",
                    errno.EADDRNOTAVAIL: "That IP address can't be assigned-to.",
                }
                try:
                    error_text = ERRORS[e.errno]
                except (AttributeError, KeyError):
                    error_text = str(e)
                sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + "\n")
                # Need to use an OS exit because sys.exit doesn't work in a thread
                os._exit(1)
            except KeyboardInterrupt:
                if shutdown_message:
                    print(shutdown_message)
                sys.exit(0)

        if use_reloader:
            try:
                from django.utils.autoreload import run_with_reloader

                run_with_reloader(inner_run)
            except ImportError:
                from django.utils import autoreload

                autoreload.main(inner_run)
        else:
            inner_run()
